// Copyright (C) 2010 Félix Cloutier.

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

#include "JitFunctionContext.h"
#include "JitRecompiler.h"

#include <llvm/Type.h>
#include <llvm/Support/TypeBuilder.h>

#include <vector>
#include <cstdio>

#include "../PowerPC.h"

#pragma mark LLVM type support
namespace llvm
{
	template<bool xcompile>
	class TypeBuilder<PowerPC::PowerPCState, xcompile>
	{
	public:
		// This method returns a LLVM Type* representation of a PowerPCState
		//	object. It needs to be accurate, or we die a painful death.
		static const StructType* get(LLVMContext& context)
		{
			const Type* llvm_i8 = llvm::Type::getInt8Ty(context);
			const Type* llvm_i32 = llvm::Type::getInt32Ty(context);
			const Type* llvm_i64 = llvm::Type::getInt64Ty(context);
			
			return StructType::get(context,
				ArrayType::get(llvm_i32, 128),					  // mojs
				ArrayType::get(llvm_i32, 32),					  // gpr
				ArrayType::get(ArrayType::get(llvm_i64, 32), 2),  // ps
				llvm_i32,										  // pc
				llvm_i64,										  // npc
				llvm_i32,										  // cr
				ArrayType::get(llvm_i8, 8),						  // cr_fast
				llvm_i32,										  // msr
				llvm_i32,										  // fpscr
				llvm_i32,										  // Exceptions
				ArrayType::get(llvm_i32, 16),					  // sr
				llvm_i32,										  // DebugCount
				ArrayType::get(llvm_i32, 1024),					  // spr
				llvm_i32,										  // dtlb_last
				ArrayType::get(llvm_i32, 128),					  // dtld_va
				ArrayType::get(llvm_i32, 128),					  // dtlb_pa
				llvm_i32,										  // itlb_last
				ArrayType::get(llvm_i32, 128),					  // itlb_va
				ArrayType::get(llvm_i32, 128),					  // itlb_pa
				NULL
			);
		}
	};
}

#pragma mark -
namespace LLVMJit
{

JitFunctionContext::JitFunctionContext(llvm::Module* module, const std::string& functionName)
	: context(module->getContext()), builder(module->getContext())
{
	const llvm::Type* u8Type =
		llvm::TypeBuilder<llvm::types::i<8>, false>::get(context);
	
	const llvm::Type* u32Type = 
		llvm::TypeBuilder<llvm::types::i<32>, false>::get(context);
	
	const llvm::Type*
		ppcState = llvm::TypeBuilder<PowerPC::PowerPCState, false>::get(context);
	
	const llvm::Type* u8Pointer = llvm::PointerType::get(u8Type, 0);
	const llvm::Type* ppcStatePointer = llvm::PointerType::get(ppcState, 0);
	
	llvm::FunctionType* jitFunctionType = JitRecompiler::CreateFunctionType(
		u32Type,
		u8Pointer, u8Pointer, ppcStatePointer, NULL);
	
	function = llvm::Function::Create(
		jitFunctionType,
		llvm::Function::ExternalLinkage,
		functionName,
		module);
		
	llvm::Function::arg_iterator iter = function->arg_begin();
	
	iter->setName("jitExecutor");
	arguments[0] = &(*iter);
	iter++;
	
	iter->setName("coroutine");
	arguments[1] = &(*iter);
	iter++;
	
	iter->setName("powerPCState");
	arguments[2] = &(*iter);
	iter++;
	
	entryBlock = llvm::BasicBlock::Create(context, "entry", function, NULL);
	
	cycleCounter = llvm::ConstantInt::get(u32Type, 0);
}

llvm::BasicBlock* JitFunctionContext::GetEntryBlock()
{
	return entryBlock;
}

llvm::BasicBlock* JitFunctionContext::GetBlock(u32 address)
{
	if (labelMap.count(address) == 0)
	{
		char blockName[10];
		sprintf(blockName, "l%08x", address);
		labelMap[address] = llvm::BasicBlock::Create(context, blockName, function, NULL);
	}
	return labelMap[address];
}

llvm::BasicBlock* JitFunctionContext::MakeConditionalBlock(bool successOfCondition)
{
	const char* boolToText[2] = {"true", "false"};
	char blockName[16];
	sprintf(blockName, "l%08x-%s", pc, boolToText[successOfCondition != false]);
	return llvm::BasicBlock::Create(context, blockName, function, NULL);
}

llvm::Value* JitFunctionContext::GetJitExecutor()
{
	return arguments[0];
}

llvm::Value* JitFunctionContext::GetCoroutine()
{
	return arguments[1];
}

llvm::Value* JitFunctionContext::GetPowerPCState()
{
	return arguments[2];
}

llvm::Function* JitFunctionContext::GetFunction()
{
	return function;
}

void JitFunctionContext::PushBranch(u32 branch)
{
	if (allBranches.count(branch) == 0)
	{
		allBranches.insert(branch);
		branchesLeft.push(branch);
	}
}

u32 JitFunctionContext::PopBranch()
{
	u32 branch = branchesLeft.top();
	branchesLeft.pop();
	return branch;
}

bool JitFunctionContext::HasMoreBranches()
{
	return !branchesLeft.empty();
}

void JitFunctionContext::SetPC(u32 pc)
{
	this->pc = pc;
}

llvm::Value* JitFunctionContext::GetCycles()
{
	return cycleCounter;
}

u32 JitFunctionContext::GetPC()
{
	return pc;
}

void JitFunctionContext::AddCycles(u32 cycles)
{
	const llvm::Type* u32Type = 
		llvm::TypeBuilder<llvm::types::i<32>, false>::get(context);
	llvm::Value* cyclesIncrement = llvm::ConstantInt::get(u32Type, cycles);
	AddCycles(cyclesIncrement);
}

void JitFunctionContext::AddCycles(llvm::Value* cyclesIncrement)
{
	cycleCounter = builder.CreateAdd(cycleCounter, cyclesIncrement);
}

}